package com.mobilottery.server;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

import org.apache.log4j.Logger;

/**
 * Provides helper functions for JPA 
 * @author fedor
 */
public class PersistenceManager {

    private static Logger log = Logger.getLogger(PersistenceManager.class);
    // caches PersistenceManager per thread
    private static ThreadLocal<PersistenceManager> instance = new ThreadLocal<PersistenceManager>();
    // global shared EntityManagerFactory
    private static EntityManagerFactory emf;
    // entityManager for this instance
    private EntityManager entityManager;

    static {
        try {
            // initialize entity manager factory
            emf = createEntityManagerFactory();
        } catch (RuntimeException ex) {
            log.error("error creating entity manager factory", ex);
        }
    }

    public static EntityManagerFactory createEntityManagerFactory() {
        emf = Persistence.createEntityManagerFactory("mobilottery-webPU");
        return emf;
    }

    /**
     * Returns shared entity manager factory
     *
     * @return
     */
    public static EntityManagerFactory getEntityManagerFactory() {
        return emf;
    }

    public PersistenceManager() {
        try {
            // check that factory was initialized (if static initialization was failed)
            if (emf == null) {
                emf = createEntityManagerFactory();
                if (emf == null) {
                    throw new RuntimeException("no entity manager factory is available");
                }
            }
        } catch (RuntimeException ex) {
            log.error("error creating manager factory", ex);
            throw ex;
        }
    }

    /**
     * Returns entity manager that is hold by this persistence manager, if no
     * entity manager was created creates new entity manager on demand
     *
     * @return
     */
    public EntityManager getEntityManager() {
        if (entityManager == null) {
            entityManager = emf.createEntityManager();
        }
        return entityManager;
    }

    /**
     * Retrieves PersistenceManager instance that is bound to the current
     * thread; if getInstance was not called by current thread creates new
     * PersistenceManager on demand and binds it to the current thread
     *
     * @return
     */
    public static PersistenceManager getInstance() {
        PersistenceManager persistenceManagerInstance = instance.get();
        if (persistenceManagerInstance == null) {
            if (log.isDebugEnabled()) {
                log.debug("creating persistence manager instance for thread " + Thread.currentThread());
            }
            persistenceManagerInstance = new PersistenceManager();
            instance.set(persistenceManagerInstance);
        }
        return persistenceManagerInstance;
    }

    /**
     * Thells whether getInstance was already called and PersistenceManager
     * instance was bound to the current thread
     *
     * @return
     */
    public static boolean isInstanceInitialized() {
        return instance.get() != null;
    }

    /**
     * Closes entity manager that was created for current PersistenceManager
     * instance (if any) and removes the instance from the thread context.
     */
    public static void closeInstance() {
        PersistenceManager persistenceManagerInstance = instance.get();
        if (persistenceManagerInstance != null) {
            try {
                if (log.isDebugEnabled()) {
                    log.debug("closing persistence manager instance for thread " + Thread.currentThread());
                }
                persistenceManagerInstance.getEntityManager().close();
                instance.set(null);
            } catch (RuntimeException rex) {
                log.error("error while closing persistence manager instance", rex);
            }
        } else {
            log.warn("closing non-existent persistence manager instance");
        }
    }

    protected Query getQuery(EntityManager session, String hsql) {
        return session.createQuery(hsql);
    }

    public <Obj> Obj getByKey(Class<Obj> clazz, Serializable key) {
        EntityManager session = null;
        Obj result = null;
        session = getEntityManager();
        result = session.find(clazz, key);
        return result;
    }

    @SuppressWarnings("unchecked")
    public <E> List<E> selectQuery(String hql) {
        log.debug("selectQuery: " + hql);
        EntityManager session = null;
        List<E> list = null;
        session = getEntityManager();
        Query query = getQuery(session, hql);
        list = query.getResultList();
        return list;
    }
    
    @SuppressWarnings("unchecked")
    public <E> E selectSingleQuery(String hql) {
        EntityManager session = null;
        session = getEntityManager();
        Query query = getQuery(session, hql);
        return (E)query.getSingleResult();
    }    
        
    
    @SuppressWarnings("unchecked")
    public <E> List<E> selectQuery(String hql,  int startIndex, int count) {
        log.debug("selectQuery: " + hql);
        EntityManager session = null;
        List<E> list = null;
        session = getEntityManager();
        Query query = getQuery(session, hql);
        query.setFirstResult(0);
        query.setMaxResults(count);
        list = query.getResultList();
        return list;
    }    

    @SuppressWarnings("unchecked")
    public <E> List<E> selectParamQuery(String hql, Map<String, Object> parameters) {
        EntityManager session = null;
        List<E> list = null;
        session = getEntityManager();
        Query query = getQuery(session, hql);
        for (String name : parameters.keySet()) {
            query.setParameter(name, parameters.get(name));
        }
        list = query.getResultList();
        return list;
    }
    
    @SuppressWarnings("unchecked")
    public <E> List<E> selectParamQuery(String hql, Map<String, Object> parameters, int startIndex, int count) {
        EntityManager session = null;
        List<E> list = null;
        session = getEntityManager();
        Query query = getQuery(session, hql);
        for (String name : parameters.keySet()) {
            query.setParameter(name, parameters.get(name));
        }
        query.setFirstResult(0);
        query.setMaxResults(count);
        list = query.getResultList();
        return list;
    }    
        

    @SuppressWarnings("unchecked")
    public <E> E selectParamSingleQuery(String hql, Map<String, Object> parameters) {
        EntityManager session = null;
        session = getEntityManager();
        Query query = getQuery(session, hql);
        for (String name : parameters.keySet()) {
            query.setParameter(name, parameters.get(name));
        }
        return (E)query.getSingleResult();
    }    
    
    /**
     * @param o persistable object
     * @throws PersistenceException
     */
    public void persist(Object o) throws PersistenceException {
        EntityManager em = PersistenceManager.getInstance().getEntityManager();
        try {
            em.getTransaction().begin();
            em.persist(o);
            em.flush();
            em.getTransaction().commit();
            em.refresh(o);
        } catch (Throwable t) {
            if (em != null) {
                try {
                    em.getTransaction().rollback();
                } catch (Throwable tt) {
                    log.debug("roll back of failed transaction has failed");
                }
            }
            if (t instanceof PersistenceException) {
                throw (PersistenceException) t;
            }
            throw new PersistenceException("error persisting entity " + o, t);
        }
    }
    
    /**
     * @param o persistable object
     * @throws PersistenceException
     */
    public void delete(Object o) throws PersistenceException {
        EntityManager em = PersistenceManager.getInstance().getEntityManager();
        try {
            em.getTransaction().begin();
            em.remove(o);
            em.flush();
            em.getTransaction().commit();
        } catch (Throwable t) {
            if (em != null) {
                try {
                    em.getTransaction().rollback();
                } catch (Throwable tt) {
                    log.debug("roll back of failed transaction has failed");
                }
            }
            if (t instanceof PersistenceException) {
                throw (PersistenceException) t;
            }
            throw new PersistenceException("error persisting entity " + o, t);
        }
    }    
}
